package client;

import static shared.Status.*;
import static shared.StatusHandler.*;

import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.net.Socket;
import java.net.UnknownHostException;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import org.w3c.dom.Document;

import server.ClientObject;
import shared.CoordObject;
import shared.Handler;
import shared.TaskObject;
import shared.TaskQueue;
import shared.XMLModem;

public class DnDClient extends Handler {
	private int port;
	private String SERVER_IP;
	private String name;
	private boolean authenticated = false;

	private boolean isAlive = false;
	private XMLModem modem = new XMLModem();

	public DnDClient() {
		// TODO Auto-generated constructor stub
		this("127.0.0.1", 7777);
	}
	
	public DnDClient(String name)
	{
		this("127.0.0.1", 7777);
		setName(name);
	}

	public DnDClient(String IP, int port) {
		setServerAddress(IP, port);
	}


	/**
	 * Start the client
	 */
	public void start() {
		updateStatus(CONNECT, SERVER_IP + " @ " + port);
		if (connectTo()) {
			updateStatus(OPEN_STREAM);
			if (openStreams()) 
			{
				isAlive = true;
				handle();
			}
		}
	}

	/**
	 * Send a message
	 * @param message the message
	 */
	public void sendMessage(String message) {
		if (canSend()) {
			send(message);
			System.out.println("Message sent");
		} else {
			System.out.println("Message cannot be sent");
		}
	}

	/**
	 * Disconnect from Server
	 */
	public void disconnect() {
		stopStreams();
		try {
			connection.close();
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}

	/**
	 * Set the port
	 * 
	 * @param port
	 */
	public void setPort(int port) {
		this.port = port;
	}

	/**
	 * Set the Server host name or IP address
	 * 
	 * @param host
	 */
	public void setServerIP(String host) {
		this.SERVER_IP = host;
	}

	/**
	 * Set the address of the server (IP,port)
	 * 
	 * @param host
	 * @param port
	 */
	public void setServerAddress(String host, int port) {
		setPort(port);
		setServerIP(host);
	}
	
	/**
	 * Set the name of client (might be required for authentication)
	 * @param name name of client
	 */
	public void setName(String name)
	{
		this.name = name;
	}

	/**
	 * Connect to Server
	 * 
	 * @return true, if successful
	 */
	private boolean connectTo() {
		try {
			// TODO: Delegate Status to Status Informer
			connection = new Socket(SERVER_IP, port);
			return true;
		} catch (UnknownHostException uhe) {
			// TODO: Delegate to a Error Handler
			// TODO: Retry
		} catch (IOException io) {
			// TODO: Delegate to a Error Handler
			// TODO: Retry
		}

		return false;
	}

	/**
	 * Handle the connection
	 */
	private void handle() {
		while (connection.isConnected() && isAlive)
		{
			if (canReceive()) {
				try 
				{
					if(authenticated)
					{
						Document doc = (Document) streamIn.readObject();
						updateStatus(RECEIVE,doc.toString());
						modem.parseDocument(doc);
						
						/*Debugging Location System*/
						TaskQueue queue = TaskQueue.getInstance();
						TaskObject object = queue.popTask();
						CoordObject cord = object.getCoordObjects().get(0);
						System.out.println(cord);
					}
					else
					{
						String received = (String) streamIn.readObject();
						updateStatus(RECEIVE, received);
						scanTextMessage(received);
					}
					
				} catch (IOException io) {
					// TODO: Delegate to a Error Handler
				} catch (ClassNotFoundException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				}
			} else {
				updateStatus(CLOSE_STREAM);
				stopStreams();
			}
		}
	}
	
	private void scanTextMessage(String message)
	{
		if(message.contains("@^Auth@")) 
		{
			Pattern pattern = Pattern.compile("~.+~");
			Matcher match = pattern.matcher(message);
			
			if(match.find())
			{
				String infos = match.group(0);				
				infos = infos.substring(1, infos.length()-2);				
				String[] key_value_pairs = infos.split(";");
				
				for(int i = 0; i<key_value_pairs.length; i++)
				{
					String[] key_value = key_value_pairs[i].split(":");
					
					if(key_value[0].equalsIgnoreCase("status"))
					{
						if(key_value[1].equals("AUTHENTICATED"))
						{
							authenticated = true;
							System.out.println("You have been authenticated");
						}
					}
				}
			}
		}
	}
	
	// Might implement protocol file
	public void sendAuthRequest()
	{
		sendMessage("@^Auth@_~nm:"+name+";vs:1.0;~");
	}

	/******************************************************
	 *  Event Listener Stuff
	 *****************************************************/
	protected javax.swing.event.EventListenerList listenerList = new javax.swing.event.EventListenerList();

	// This methods allows classes to register for MyEvents
	public void addMyEventListener(ClientListener listener) {
		listenerList.add(ClientListener.class, listener);
	}

	// This methods allows classes to unregister for MyEvents
	public void removeMyEventListener(ClientListener listener) {
		listenerList.remove(ClientListener.class, listener);
	}

	// This private class is used to fire MyEvents
	void fireClientEvent(ClientEvent evt) {
		Object[] listeners = listenerList.getListenerList();
		// Each listener occupies two elements - the first is the listener class
		// and the second is the listener instance
		for (int i = 0; i < listeners.length; i += 2) {
			if (listeners[i] == ClientListener.class) {
				((ClientListener) listeners[i + 1]).ClientEventPerformed(evt);
			}
		}
	}
}
